#!/usr/bin/env python
# Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
#
# This python script creates a version string in a C++ file.

from __future__ import print_function

import hashlib
import os
import sys
import time
from optparse import OptionParser
import utils


def debugLog(message):
    print(message, file=sys.stderr)
    sys.stderr.flush()


# When these files change, snapshots created by the VM are potentially no longer
# backwards-compatible.
VM_SNAPSHOT_FILES = [
    # Header files.
    'clustered_snapshot.h',
    'datastream.h',
    'image_snapshot.h',
    'object.h',
    'raw_object.h',
    'snapshot.h',
    'snapshot_ids.h',
    'symbols.h',
    # Source files.
    'clustered_snapshot.cc',
    'dart.cc',
    'dart_api_impl.cc',
    'image_snapshot.cc',
    'object.cc',
    'raw_object.cc',
    'raw_object_snapshot.cc',
    'snapshot.cc',
    'symbols.cc',
]


def MakeVersionString(quiet, no_git_hash, custom_for_pub=None):
    channel = utils.GetChannel()
    if custom_for_pub:
        # TODO(athom): remove the custom 2.7.0 logic post release.
        # For 2.7.0, we want flutter to claim Dart is 2.7.0 even before it is
        # decided what exactly 2.7.0 will be. Dart & Flutter stable releases
        # will be synced, so that what will be released as Dart 2.7.0 will also
        # be what will be packaged with Flutter.
        version = utils.ReadVersionFile()
        custom_version_string = "%s.%s.%s" % (version.major, version.minor,
                                              version.patch)
        if custom_version_string == "2.7.0" and custom_for_pub == "flutter":
            return "2.7.0"

    if custom_for_pub and channel == 'be':
        latest = utils.GetLatestDevTag()
        if not latest:
            # If grabbing the dev tag fails, then fall back on the VERSION file.
            latest = utils.GetSemanticSDKVersion(no_git_hash=True)
        if no_git_hash:
            version_string = ("%s.%s" % (latest, custom_for_pub))
        else:
            git_hash = utils.GetShortGitHash()
            version_string = ("%s.%s-%s" % (latest, custom_for_pub, git_hash))
    else:
        version_string = utils.GetSemanticSDKVersion(no_git_hash=no_git_hash)
    if not quiet:
        debugLog("Returning version string: %s " % version_string)
    return version_string


def MakeSnapshotHashString():
    vmhash = hashlib.md5()
    for vmfilename in VM_SNAPSHOT_FILES:
        vmfilepath = os.path.join(utils.DART_DIR, 'runtime', 'vm', vmfilename)
        with open(vmfilepath) as vmfile:
            vmhash.update(vmfile.read().encode('utf-8'))
    return vmhash.hexdigest()


def MakeFile(quiet,
             output_file,
             input_file,
             no_git_hash,
             custom_for_pub,
             version_file=None):
    if version_file:
        version_string = utils.GetVersion(no_git_hash, version_file)
    else:
        version_string = MakeVersionString(quiet, no_git_hash, custom_for_pub)

    version_cc_text = open(input_file).read()
    version_cc_text = version_cc_text.replace("{{VERSION_STR}}", version_string)
    version_time = utils.GetGitTimestamp()
    if no_git_hash or version_time == None:
        version_time = "Unknown timestamp"
    version_cc_text = version_cc_text.replace("{{COMMIT_TIME}}", version_time.decode("utf-8"))
    abi_version = utils.GetAbiVersion(version_file)
    version_cc_text = version_cc_text.replace("{{ABI_VERSION}}", abi_version)
    oldest_supported_abi_version = utils.GetOldestSupportedAbiVersion(
        version_file)
    version_cc_text = version_cc_text.replace(
        "{{OLDEST_SUPPORTED_ABI_VERSION}}", oldest_supported_abi_version)
    snapshot_hash = MakeSnapshotHashString()
    version_cc_text = version_cc_text.replace("{{SNAPSHOT_HASH}}",
                                              snapshot_hash)
    open(output_file, 'w').write(version_cc_text)
    return True


def main(args):
    try:
        # Parse input.
        parser = OptionParser()
        parser.add_option(
            "--custom_for_pub",
            action="store",
            type="string",
            help=("Generates a version string that works with pub that includes"
                  "the given string. This is silently ignored on channels other"
                  "than be"))
        parser.add_option(
            "--input",
            action="store",
            type="string",
            help="input template file.")
        parser.add_option(
            "--no_git_hash",
            action="store_true",
            default=False,
            help="Don't try to determine svn revision")
        parser.add_option(
            "--output", action="store", type="string", help="output file name")
        parser.add_option(
            "-q",
            "--quiet",
            action="store_true",
            default=False,
            help="disable console output")
        parser.add_option(
            "--version-file",
            action="store",
            type="string",
            default=None,
            help="Version file")

        (options, args) = parser.parse_args()

        # If there is no input template, then write the bare version string to
        # options.output. If there is no options.output, then write the version
        # string to stdout.
        if not options.input:
            version_string = MakeVersionString(
                options.quiet, options.no_git_hash, options.custom_for_pub)
            if options.output:
                open(options.output, 'w').write(version_string)
            else:
                print(version_string)
            return 0

        if not options.output:
            sys.stderr.write('--output not specified\n')
            return -1
        if not options.input:
            sys.stderr.write('--input not specified\n')
            return -1

        files = []
        for arg in args:
            files.append(arg)

        if not MakeFile(options.quiet, options.output, options.input,
                        options.no_git_hash, options.custom_for_pub,
                        options.version_file):
            return -1

        return 0
    except Exception as inst:
        sys.stderr.write('make_version.py exception\n')
        sys.stderr.write(str(inst))
        sys.stderr.write('\n')
        return -1


if __name__ == '__main__':
    exit_code = main(sys.argv)
    sys.exit(exit_code)
